import datetime
import json
import traceback
from dataclasses import dataclass
from typing import Generic, Optional, Any, Union, List

from aiohttp import web
import aiohttp_cors
from aiohttp.web_exceptions import HTTPPermanentRedirect, HTTPTemporaryRedirect

from .default_documentation_handler import generate_documentation_handler
from .request_handler import HttpEndpointDescriptor, ContextType
from ..logger.abstract_logger import AbstractLogger
from ..jsonpat.jsonize import to_json


class DefaultServer(Generic[ContextType]):
    app: web.Application
    site: web.TCPSite
    runner: web.AppRunner
    logger: AbstractLogger
    port: int
    context: ContextType

    @classmethod
    async def create(
        cls,
        *,
        logger: AbstractLogger,
        handlers: List[HttpEndpointDescriptor],
        context: ContextType,
        host: Optional[str] = None,
        port: int,
    ):
        self = cls()
        self.port = port
        self.context = context
        self.logger = logger

        self.app = web.Application(middlewares=[
            web.normalize_path_middleware(merge_slashes=True, append_slash=False, remove_slash=True, redirect_class=HTTPTemporaryRedirect)
        ])

        for handler in handlers:
            self.app.add_routes([ web.route(handler.method, handler.path, self._wrap_http_handler(handler)) ])
            logger.info(f"创建接口  {handler.method}  {handler.path}")
        self.app.add_routes([ web.get("/", generate_documentation_handler(handlers)) ])

        cors = aiohttp_cors.setup(self.app, defaults={
            "*": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                allow_headers="*",
                expose_headers="*"
            )
        })
        for route in self.app.router.routes():
            cors.add(route)

        self.runner = web.AppRunner(self.app)
        await self.runner.setup()

        self.site = web.TCPSite(
            self.runner,
            host=host if host is not None else "0.0.0.0",
            port=port
        )
        await self.site.start()
        logger.info(f"服务器已启动，端口 {port}")

        return self

    def _wrap_http_handler(
        self, f: HttpEndpointDescriptor
    ):
        async def inner(request) -> web.Response:
            self.logger.debug(f"调用 [{f.method}] {f.path}")

            response: web.Response
            try:
                if not hasattr(f, "_prepared"):
                    await f.on_prepare(context=self.context, logger=self.logger)
                    setattr(f, "_prepared", True)

                response = await f.on_request(context=self.context, logger=self.logger, request=request)
                if response is None:
                    response = web.Response()

            except Exception as e:
                errcode = getattr(e, "errcode", "unknown")
                message = getattr(e, "message", None)
                details = getattr(e, "details", None)
                if message is None:
                    status = 500  # internal error
                    self.logger.error(f"发生未知错误: {e}\n{traceback.format_exc()}")
                    message = datetime.datetime.now().strftime("%m-%dT%H:%M:%S") + " 发生未知错误，请联系管理员"
                else:
                    status = 403  # forbidden

                body_json = to_json(ErrorResponse(errcode, message, details))
                body_str = json.dumps(body_json, ensure_ascii=False)
                response = web.Response(body=body_str, content_type="application/json", status=status)

                self.logger.debug(f"调用 [{f.method}] {f.path} 失败，结果为 {body_str[:400]}")

            return response

        return inner

    async def stop(self):
        await self.site.stop()
        await self.runner.cleanup()


@dataclass
class ErrorResponse:
    errcode: Union[str, int]
    message: Union[str, dict[str, str]]
    details: Optional[Any]


@dataclass
class NormalResponse:
    result: Any